package com.xyzwps.lib.jdbc.method2sql;

import com.xyzwps.lib.jdbc.DbException;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.xyzwps.lib.dollar.Dollar.*;

public final class Tokenizer {

    private final Pattern pattern;

    private static final List<String> DEFAULT_WORDS = List.of(
            "NotEqual",
            "Equal",
            "GreaterThan",
            "GreaterThanOrEqual",
            "LessThan",
            "LessThanOrEqual"
    );

    private static final Comparator<String> LENGTH_FIRST_COMPARATOR = (lhs, rhs) -> {
        var lenDiff = rhs.length() - lhs.length();
        if (lenDiff != 0) {
            return lenDiff;
        }
        return rhs.compareTo(lhs);
    };

    private Tokenizer(List<String> words) {
        var all = $(DEFAULT_WORDS).concat(words).value();
        var mid = all.stream()
                .sorted(LENGTH_FIRST_COMPARATOR)
                .map(it -> "(" + it + ")")
                .collect(Collectors.joining("|"));
        var regexp = "(([a-z]+)|" + mid + "|([A-Z][a-z0-9]*))";
        this.pattern = Pattern.compile(regexp);
    }

    public static Tokenizer fromWords(List<String> words) {
        return new Tokenizer($.isEmpty(words) ? List.of() : words);
    }

    private List<String> pieces(String methodName) {
        List<String> elements = new ArrayList<>();
        var matcher = pattern.matcher(methodName);
        while (matcher.find()) {
            elements.add(matcher.group());
        }
        if (!String.join("", elements).equals(methodName)) {
            throw new DbException("Invalid method name " + methodName);
        }
        return elements;
    }

    public List<Token> tokenize(String methodName) {
        var pieces = pieces(methodName);
        var tokens = new ArrayList<Token>();
        for (int i = 0; i < pieces.size(); i++) {
            var p = pieces.get(i);
            if (i == 0) {
                tokens.add(switch (p) {
                    case "find" -> Token.KeyWord.FIND;
                    case "get" -> Token.KeyWord.GET;
                    case "count" -> Token.KeyWord.COUNT;
                    case "update" -> Token.KeyWord.UPDATE;
                    case "delete" -> Token.KeyWord.DELETE;
                    default -> throw new DbException("Unsupported method " + methodName);
                });
            } else {
                tokens.add(switch (p) {
                    case "By" -> Token.KeyWord.BY;
                    case "Where" -> Token.KeyWord.WHERE;
                    case "And" -> Token.KeyWord.AND;
                    case "Or" -> Token.KeyWord.OR;
                    case "Eq", "Equal" -> Token.KeyWord.EQ;
                    case "Ne", "NotEqual" -> Token.KeyWord.NE;
                    case "Gt", "GreaterThan" -> Token.KeyWord.GT;
                    case "Ge", "GreaterThanOrEqual" -> Token.KeyWord.GE;
                    case "Lt", "LessThan" -> Token.KeyWord.LT;
                    case "Le", "LessThanOrEqual" -> Token.KeyWord.LE;
                    case "Like" -> Token.KeyWord.LIKE;
                    case "Not" -> Token.KeyWord.NOT;
                    case "In" -> Token.KeyWord.IN;
                    case "Between" -> Token.KeyWord.BETWEEN;
                    case "Is" -> Token.KeyWord.IS;
                    case "Null" -> Token.KeyWord.NULL;
                    case "Order" -> Token.KeyWord.ORDER;
                    case "Limit" -> Token.KeyWord.LIMIT;
                    case "Asc" -> Token.KeyWord.ASC;
                    case "Desc" -> Token.KeyWord.DESC;
                    case "Set" -> Token.KeyWord.SET;
                    default -> new Token.Name(p);
                });
            }
        }
        return tokens;
    }
}